/*
 * image_app.cpp
 *
 *  Created on: May 17, 2010
 *      Author: z1083248
 */

#include "config.hpp"
#include "image_app.hpp"
#include "fft_basic.hpp"
#include "fft_basic_redux.hpp"
#include "fft_radix2.hpp"
#include "fft_radix4.hpp"
#include "fft_radix64.hpp"
#include "fft_ops.hpp"
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <complex>
#include <fftw3.h>

//using namespace cimg_library;
using namespace std;
using namespace FftLib::Common;

// TODO: Check for a trailing slash at the end of the imageDirectory_

namespace TestAppFftLib
{

const char * const ImageApp::BasicFftDataName = "BasicFft.raw";
const char * const ImageApp::BasicReduxFftDataName = "BasicReduxFft.raw";
const char * const ImageApp::BasicReduxFftComplexDataName = "BasicReduxFftComplex.raw";
const char * const ImageApp::BasicRadix2DataName = "BasixRadix2Fft.raw";
const char * const ImageApp::FftwDataName = "Fftw.raw";

const float ImageApp::factor_ = 1.0 / 4096.0;

const unsigned int ImageApp::nn_[2] =
{ windowWidth_, windowWidth_ };

const float ImageApp::demeanReg_[25] =
{ -0.0100, -0.0230, -0.0300, -0.0230, -0.0100, -0.0230, -0.0529, -0.0690,
      -0.0529, -0.0230, -0.0300, -0.0690, 0.8316, -0.0690, -0.0300, -0.0230,
      -0.0529, -0.0690, -0.0529, -0.0230, -0.0100, -0.0230, -0.0300, -0.0230,
      -0.0100 };

ImageApp::ImageApp() :
   isImageOneLoaded_(false), isImageTwoLoaded_(false), isFftInitialized_(0),
         imageDirectory_(0), image1ul_(0), image2ul_(0)
{
   // init arrays
   memset(imageOne_, 0, imageArea_ * sizeof(int));
   memset(imageTwo_, 0, imageArea_ * sizeof(int));
   memset(twoD_Hanning_, 0, windowArea_ * sizeof(float));
   memset(correlationSurface_, 0, complexWindowArea_ * sizeof(float));
   memset(bitReverseTable1D_, 0, complexWindowWidth_ * sizeof(int));
   memset(bitReverseTable2D_, 0, complexWindowArea_ * sizeof(int));
   memset(regHpFilter_, 0, complexWindowArea_ * sizeof(float));

   // set OS dependent slashCharacter (macro can be defined in config.hpp
#if defined(LINUX)
   slashCharacter_ = new string("/");
#else
   slashCharacter_ = new char[2];
   slashCharacter_[0] = '\\';
   slashCharacter_[1] = '\0';
#endif
}

void ImageApp::loadImage(ImageType imageType, const char * const imagePath)
{
   string strBuffer;
   ifstream myfile(imagePath);
   int index = 0;

   switch (imageType)
   {
   case TestAppFftLib::ImageOne:
      //imageOne_ = new CImg<int> (imagePath);
      while (myfile.good())
      {
         getline(myfile, strBuffer);
         imageOne_[index++] = atoi(strBuffer.c_str());
         strBuffer.clear();
      }
      isImageOneLoaded_ = true;
      break;
   default: // case ImageApp::ImageTwo:
      //imageTwo_ = new CImg<int> (imagePath);
      while (myfile.good())
      {
         getline(myfile, strBuffer);
         imageTwo_[index++] = atoi(strBuffer.c_str());
         strBuffer.clear();
      }
      isImageTwoLoaded_ = true;
      break;
   }
}

int ImageApp::findMax(int argOne, int argTwo)
{
   return (argOne < argTwo ? argTwo : argOne);
}

int ImageApp::findMin(int argOne, int argTwo)
{
   return (argOne > argTwo ? argTwo : argOne);
}

int ImageApp::registrationTruncateOffset(int offset, int maxMove)
{
   if (offset < 0)
   {
      return findMax(offset, -maxMove);
   }
   else
   {
      return findMin(offset, maxMove);
   }
}

void ImageApp::registrationReturnUpperLeft(int rowOffset, int colOffset,
      int *upperLeft)
{
   int u1Row;
   int u1Col;
   int u1RowOffset;
   int u1ColOffset;
   int truncRow;
   int truncCol;

   u1RowOffset = (numberOfRows_ - windowWidth_) / 2;
   u1ColOffset = (numberOfColumns_ - windowWidth_) / 2;

   truncRow = registrationTruncateOffset(rowOffset, windowMaxMoveUd_);
   truncCol = registrationTruncateOffset(colOffset, windowMaxMoveLr_);

   u1Row = u1RowOffset + truncRow;
   u1Col = u1ColOffset + truncCol;

   *upperLeft = (u1Row * numberOfColumns_) + u1Col;
}

void ImageApp::executeBasicFft()
{
   FftLib::Basic::Fft fft;
   int ulImg1;
   int ulImg2;

   static bool isInit = false;

   if (!isInit)
   {
      // Initialize tables
      fft.generateBitReverse1dTable(bitReverseTable1D_, maxPower_);
      fft.generateBitReverse2dTable(bitReverseTable2D_, bitReverseTable1D_,
            windowWidth_, windowWidth_);
      fft.generateHanning2dFilter(twoD_Hanning_, windowWidth_, windowWidth_);

      // Setup filter
      fft.setup2dFilter(demeanReg_, dri_, drj_, regHpFilter_, windowWidth_,
            windowWidth_, factor_);
      fft.decimationInFrequency(regHpFilter_, nn_, 2, -1);
      fft.inPlaceMultiplication(regHpFilter_, windowWidth_, windowWidth_);

      isInit = true;
   }

   // perform actual FFT
   registrationReturnUpperLeft(0, 0, &ulImg1);
   registrationReturnUpperLeft(0, 0, &ulImg2);

   //image1ul_ = imageOne_->data() + ulImg1;
   //image2ul_ = imageTwo_->data() + ulImg2;

   image1ul_ = imageOne_ + ulImg1;
   image2ul_ = imageTwo_ + ulImg2;

   fft.pack2dWindow(image1ul_, image2ul_, numberOfColumns_, windowWidth_,
         windowWidth_, twoD_Hanning_, twoD_Hanning_, zeroPadWidth_,
         correlationSurface_);
   fft.decimationInFrequencyNoBitReverse(correlationSurface_, nn_, 2, -1);
   fft.inPlaceMultiplication(correlationSurface_, (int *) bitReverseTable2D_,
         windowWidth_, windowWidth_, regHpFilter_);
   fft.decimationInTimeNoBitReverse(correlationSurface_, nn_, 2, 1);
}

void ImageApp::executeBasicReduxFft()
{
   FftLib::BasicRedux::Fft fft;
   int ulImg1;
   int ulImg2;
   FftLib::Common::PsiTable psiTableDif[12];
   FftLib::Common::PsiTable psiTableDit[12];

   static bool isInit = false;

   if (!isInit)
   {
      // Initialize tables
      fft.generateBitReverse1dTable(bitReverseTable1D_, maxPower_);
      fft.generateBitReverse2dTable(bitReverseTable2D_, bitReverseTable1D_,
            windowWidth_, windowWidth_);
      fft.generateHanning2dFilter(twoD_Hanning_, windowWidth_, windowWidth_);

      // Setup filter
      fft.setup2dFilter(demeanReg_, dri_, drj_, regHpFilter_, windowWidth_,
            windowWidth_, factor_);
      fft.decimationInFrequency(regHpFilter_, nn_, 2, -1);
      fft.inPlaceMultiplication(regHpFilter_, windowWidth_, windowWidth_);

      fft.create2dPsiTableDif(psiTableDif, windowWidth_, -1);
      fft.create2dPsiTableDit(psiTableDit, windowWidth_, 1);

      isInit = true;
   }

   // perform actual FFT
   registrationReturnUpperLeft(0, 0, &ulImg1);
   registrationReturnUpperLeft(0, 0, &ulImg2);

   image1ul_ = imageOne_ + ulImg1;
   image2ul_ = imageTwo_ + ulImg2;

   fft.pack2dWindow(image1ul_, image2ul_, numberOfColumns_, windowWidth_,
         windowWidth_, twoD_Hanning_, twoD_Hanning_, zeroPadWidth_,
         correlationSurface_);
   fft.decimationInFrequencyNoBitReverse4096(correlationSurface_, psiTableDif);
   fft.inPlaceMultiplication(correlationSurface_, (int *) bitReverseTable2D_,
         windowWidth_, windowWidth_, regHpFilter_);
   fft.decimationInTimeNoBitReverse4096(correlationSurface_, psiTableDit);
}

void ImageApp::executeBasicReduxComplexFft()
{
   FftLib::BasicRedux::Fft fft;
   int ulImg1;
   int ulImg2;
   complex<float> complexData[windowArea_];
   FftLib::Common::PsiTable psiTableDif[12];
   FftLib::Common::PsiTable psiTableDit[12];

   static bool isInit = false;

   if (!isInit)
   {
      // Initialize tables
      fft.generateBitReverse1dTable(bitReverseTable1D_, maxPower_);
      fft.generateBitReverse2dTable(bitReverseTable2D_, bitReverseTable1D_,
            windowWidth_, windowWidth_);
      fft.generateHanning2dFilter(twoD_Hanning_, windowWidth_, windowWidth_);

      // Setup filter
      fft.setup2dFilter(demeanReg_, dri_, drj_, regHpFilter_, windowWidth_,
            windowWidth_, factor_);
      fft.decimationInFrequency(regHpFilter_, nn_, 2, -1);
      fft.inPlaceMultiplication(regHpFilter_, windowWidth_, windowWidth_);

      fft.create2dPsiTableDif(psiTableDif, windowWidth_, -1);
      fft.create2dPsiTableDit(psiTableDit, windowWidth_, 1);

      isInit = true;
   }

   // perform actual FFT
   registrationReturnUpperLeft(0, 0, &ulImg1);
   registrationReturnUpperLeft(0, 0, &ulImg2);

   image1ul_ = imageOne_ + ulImg1;
   image2ul_ = imageTwo_ + ulImg2;

   fft.pack2dWindow(image1ul_, image2ul_, numberOfColumns_, windowWidth_,
         windowWidth_, twoD_Hanning_, twoD_Hanning_, zeroPadWidth_,
         correlationSurface_);
   fft.decimationInFrequencyNoBitReverse4096(correlationSurface_, psiTableDif);
   fft.inPlaceMultiplication(correlationSurface_, (int *) bitReverseTable2D_,
         windowWidth_, windowWidth_, regHpFilter_);

   convertDataToComplex(correlationSurface_, complexData, complexWindowArea_);
   fft.decimationInTimeNoBitReverse4096Complex(complexData, psiTableDit);
   convertComplexToData(complexData, correlationSurface_, complexWindowArea_);
}

void ImageApp::executeRadix2Fft()
{
   FftLib::BasicRedux::Fft fft;
   FftLib::BasicRadix2::Fft fft2;
   complex<float> complexData[windowArea_];
   int ulImg1;
   int ulImg2;
   FftLib::Common::PsiTable psiTableDif[12];
   FftLib::Common::PsiTable psiTableDit[12];

   static bool isInit = false;

   if (!isInit)
   {
      // Initialize tables
      fft.generateBitReverse1dTable(bitReverseTable1D_, maxPower_);
      fft.generateBitReverse2dTable(bitReverseTable2D_, bitReverseTable1D_,
            windowWidth_, windowWidth_);
      fft.generateHanning2dFilter(twoD_Hanning_, windowWidth_, windowWidth_);

      // Setup filter
      fft.setup2dFilter(demeanReg_, dri_, drj_, regHpFilter_, windowWidth_,
            windowWidth_, factor_);
      fft.decimationInFrequency(regHpFilter_, nn_, 2, -1);
      fft.inPlaceMultiplication(regHpFilter_, windowWidth_, windowWidth_);

      fft.create2dPsiTableDif(psiTableDif, windowWidth_, -1);
      fft.create2dPsiTableDit(psiTableDit, windowWidth_, 1);

      isInit = true;
   }

   // perform actual FFT
   registrationReturnUpperLeft(0, 0, &ulImg1);
   registrationReturnUpperLeft(0, 0, &ulImg2);

   image1ul_ = imageOne_ + ulImg1;
   image2ul_ = imageTwo_ + ulImg2;

   fft.pack2dWindow(image1ul_, image2ul_, numberOfColumns_, windowWidth_,
         windowWidth_, twoD_Hanning_, twoD_Hanning_, zeroPadWidth_,
         correlationSurface_);
   fft.decimationInFrequencyNoBitReverse4096(correlationSurface_, psiTableDif);
   fft.inPlaceMultiplication(correlationSurface_, (int *) bitReverseTable2D_,
         windowWidth_, windowWidth_, regHpFilter_);

   //   fft.decimationInTimeNoBitReverse4096(correlationSurface_, psiTableDit);
   convertDataToComplex(correlationSurface_, complexData, complexWindowArea_);
   fft2.decimationInTimeRadix2DepthFirst2d4096(complexData, log2WindowArea_, -1);
   convertComplexToData(complexData, correlationSurface_, complexWindowArea_);
}

void ImageApp::executeFftw()
{
   FftLib::BasicRedux::Fft fft;

   fftwf_complex complexData[windowArea_] =
   {
   { 0.0, 0.0 } };

   fftwf_complex out[windowArea_] =
   {
   { 0.0, 0.0 } };

   int ulImg1 = 0;
   int ulImg2 = 0;

   FftLib::Common::PsiTable psiTableDif[12] =
   {
   { 0.0, 0.0 } };

   FftLib::Common::PsiTable psiTableDit[12] =
   {
   { 0.0, 0.0 } };

   static bool isInit = false;

   if (!isInit)
   {
      // Initialize tables
      fft.generateBitReverse1dTable(bitReverseTable1D_, maxPower_);
      fft.generateBitReverse2dTable(bitReverseTable2D_, bitReverseTable1D_,
            windowWidth_, windowWidth_);
      fft.generateHanning2dFilter(twoD_Hanning_, windowWidth_, windowWidth_);

      // Setup filter
      fft.setup2dFilter(demeanReg_, dri_, drj_, regHpFilter_, windowWidth_,
            windowWidth_, factor_);
      fft.decimationInFrequency(regHpFilter_, nn_, 2, -1);
      fft.inPlaceMultiplication(regHpFilter_, windowWidth_, windowWidth_);

      fft.create2dPsiTableDif(psiTableDif, windowWidth_, -1);
      fft.create2dPsiTableDit(psiTableDit, windowWidth_, 1);

      isInit = true;
   }

   // perform actual FFT
   registrationReturnUpperLeft(0, 0, &ulImg1);
   registrationReturnUpperLeft(0, 0, &ulImg2);

   image1ul_ = imageOne_ + ulImg1;
   image2ul_ = imageTwo_ + ulImg2;

   fft.pack2dWindow(image1ul_, image2ul_, numberOfColumns_, windowWidth_,
         windowWidth_, twoD_Hanning_, twoD_Hanning_, zeroPadWidth_,
         correlationSurface_);
   fft.decimationInFrequencyNoBitReverse4096(correlationSurface_, psiTableDif);
   fft.inPlaceMultiplication(correlationSurface_, (int *) bitReverseTable2D_,
         windowWidth_, windowWidth_, regHpFilter_);

   convertDataToComplex(correlationSurface_, complexData, complexWindowArea_);
   // the plan only needs to be generated once (inited once and then used many times)
   fftwf_plan p = fftwf_plan_dft_2d(windowWidth_, windowWidth_, complexData,
         out, FFTW_BACKWARD, FFTW_MEASURE | FFTW_DESTROY_INPUT);
   convertDataToComplex(correlationSurface_, complexData, complexWindowArea_);
   // this is the actual FFT computation
   fftwf_execute(p);
   convertComplexToData(out, correlationSurface_, complexWindowArea_);
}

void ImageApp::writeData()
{
   writeData(BasicFftDataName);
}

void ImageApp::writeData(const char * const filename)
{
   string fullFilePath(*imageDirectory_);
   fullFilePath.append(*slashCharacter_);
   fullFilePath.append(filename);

   ofstream myfile;
   myfile.open(fullFilePath.c_str());

   for (int index = 0; index < complexWindowArea_; ++index)
   {
      myfile << correlationSurface_[index] << endl;
   }

   myfile.close();
}

void ImageApp::writeImageSourceData()
{
   //writeImageSourceData("fft_sample_01.raw", imageOne_->data());
   //writeImageSourceData("fft_sample_02.raw", imageTwo_->data());
}

void ImageApp::writeImageSourceData(const char * const filename, int *data)
{
   string fullFilePath(*imageDirectory_);
   fullFilePath.append(*slashCharacter_);
   fullFilePath.append(filename);

   ofstream myfile;
   myfile.open(fullFilePath.c_str(), ios::out | ios::binary | ios::trunc);

   for (int index = 0; index < imageArea_; ++index)
   {
      myfile << data[index] << endl;
   }

   myfile.close();
}

bool ImageApp::getAllImagesLoaded()
{
   return (isImageOneLoaded_ && isImageTwoLoaded_);
}

void ImageApp::setImageDirectory(const char * const imageDirectory)
{
   if (imageDirectory_ != 0)
   {
      delete imageDirectory_;
   }

   imageDirectory_ = new string(imageDirectory);
}

}
